package com.btb.adapter.service;

import static java.util.stream.Collectors.toList;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import com.btb.adapter.constant.Constants;
import com.btb.adapter.constant.QueryType;
import com.btb.adapter.contract.ActFishService;
import com.btb.adapter.contract.ActHeroService;
import com.btb.adapter.exception.GameException;
import com.btb.adapter.mapper.OffChainMapper;
import com.btb.adapter.model.OffChain;
import com.btb.adapter.model.OffChainExample;
import com.btb.adapter.utils.Fmt;
import com.btb.adapter.utils.Web3jUtils;
import com.btb.adapter.vo.FishDetail;
import com.btb.adapter.vo.HeroDetail;
import com.btb.adapter.vo.request.OffChainRequest;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class OffChainService {
	@Autowired
	private OffChainMapper offChainMapper;
	@Autowired
	private ActHeroService actHeroService;
	@Autowired
	private ActFishService actFishService;
	@Autowired
	public Credentials credentials;

	private boolean checkStatus(OffChainRequest request) {
		Integer type = request.getType();
		QueryType chType = QueryType.type(type);
		if (Objects.isNull(chType)) {
			return false;
		}
		if (chType == QueryType.HERO) {
			return forCheckHero(request.getList());
		}
		if (chType == QueryType.FISH) {
			return forCheckFish(request.getList());
		}
		return true;
	}

	// 去链上查询英雄信息
	public boolean forCheckHero(List<Long> list) {
		boolean result = true;
		for (Long l : list) {
			HeroDetail heroDetail = actHeroService.heroDetail(new BigInteger(String.valueOf(l)));
			if (Objects.isNull(heroDetail)) {
				result = false;
				break;
			}
		}
		return result;
	}

	// 去链上查询鱼信息
	public boolean forCheckFish(List<Long> list) {
		boolean result = true;
		for (Long l : list) {
			FishDetail fishDetail = actFishService.fishDetail(new BigInteger(String.valueOf(l)));
			if (Objects.isNull(fishDetail)) {
				result = false;
				break;
			}
		}
		return result;
	}

	private void checkBatchSize(OffChainRequest request) {
		Boolean checkSize = Optional.ofNullable(request).filter(Objects::nonNull).map(OffChainRequest::getList)
				.filter(Objects::nonNull).map(x -> x.size() >= Constants.OFFCHAIN_BATCH_SIZE).orElse(Boolean.FALSE);
		if (!checkSize) {
			throw new GameException("checkBatchSize error");
		}
	}

	public String insert(OffChainRequest request) {
		// TODO 下链请求处理之前, 应该去链上查询一下, 看看是不是在链上
		String requestId = UUID.randomUUID().toString();
		boolean check = QueryType.checkType(request.getType());
		if (!check) {
			throw new GameException("type param error");
		}
		checkBatchSize(request);
		if (!checkStatus(request)) {
			throw new GameException("type param error");
		}
		offChainMapper.insertSelective(request.build(requestId));
		return requestId;
	}

	public List<OffChain> query() {
		// 根据批次来下下链
		Page<OffChain> page = PageHelper.startPage(1, Constants.OFF_CHAIN_PAGE_SIZE).doSelectPage(() -> {
			OffChainExample example = new OffChainExample();
			example.createCriteria().andStatusEqualTo(Constants.OFF_CHAIN_STATU_ZERO); // 只查未上链的
			offChainMapper.selectByExample(example);
		});
		return page.getResult();
	}

	@Transactional(rollbackFor = Exception.class)
	public void offChain() {
		final List<OffChain> list = query();
		list.forEach(x -> {
			offChain(x);
		});
	}

	private List<BigInteger> strToListBig(final String clientId) {
		return Arrays.stream(StringUtils.split(clientId, ",")).filter(Objects::nonNull).map(x -> new BigInteger(x))
				.collect(toList());
	}
	private List<Long> strToListLong(final String clientId) {
		return Arrays.stream(StringUtils.split(clientId, ",")).filter(Objects::nonNull).map(Long::parseLong)
				.collect(toList());
	}
	private <T> T getFileVal(final TransactionReceipt receipt, Function<? super TransactionReceipt, ? extends T> field) {
		return Optional.ofNullable(receipt).map(field).orElse(null);
	}
	private int offChainUpdate(final Long id, final TransactionReceipt receipt, final String status,
			final String errMsg) {
		final String transactionHash = getFileVal(receipt, TransactionReceipt::getTransactionHash);
		final BigInteger gasUsed = getFileVal(receipt, TransactionReceipt::getGasUsed);
		final BigDecimal gasUsedVal  = (gasUsed == null ? BigDecimal.ZERO : new BigDecimal(Web3jUtils.fmt(gasUsed)));
		OffChain build = OffChain.builder().id(id).status(status).startUpChainTimestamp(new Date().getTime())
				.transactionHash(transactionHash).gasUsed(gasUsedVal)
				.address(credentials.getAddress()).errDesc(errMsg).build(); // 更新为下链中
		offChainMapper.updateByPrimaryKeySelective(build);
		return 0;
	}

	public void offChain(final OffChain offChain) {
		final Integer type = offChain.getType();
		final String owner = offChain.getOwner();
		final Long id = offChain.getId();
		final String clientId = offChain.getClientId();
		// 应该去查一下,看是不是已经在链上了
		OffChain build = OffChain.builder().id(id).status(Constants.OFF_CHAIN_STATUS_ONE).build(); // 更新为下链中
		offChainMapper.updateByPrimaryKeySelective(build);
		List<BigInteger> tokenIds = strToListBig(clientId);
		if (tokenIds.isEmpty()) {
			return;
		}
		TransactionReceipt receipt = null;
		// 1英雄 2鱼
		if (Objects.equals(type, 1)) {
			// 去链上查询看是否在链上
			boolean forCheckHero = forCheckHero(strToListLong(clientId));
			if(!forCheckHero) {
				final String msg = String.format("下链失败,%s,不全部在链上", clientId);
				offChainUpdate(id, receipt, Constants.OFF_CHAIN_STATUS_FOUE, msg);
				return;
			}
			receipt = actHeroService.offChain(owner, tokenIds, Fmt.logToBig(id));
		} else if (Objects.equals(type, 2)) {
			boolean forCheckFish = forCheckFish(strToListLong(clientId));
			if(!forCheckFish) {
				final String msg = String.format("下链失败,%s,不全部在链上", clientId);
				offChainUpdate(id, receipt, Constants.OFF_CHAIN_STATUS_FOUE, msg);
				return;
			}
			receipt = actFishService.offChain(owner, tokenIds, Fmt.logToBig(id));
		} else {
			log.error("offChain err type:{}", type);
		}
		if (Objects.isNull(receipt)) {
			offChainUpdate(id, receipt, Constants.OFF_CHAIN_STATUS_FOUE, "下链失败");
			return;
		}
		offChainUpdate(id, receipt, Constants.OFF_CHAIN_STATUS_TWO, "下链待确认");
	}
}
